AWS CloudShell のホームディレクトリとずっと一緒にいる方法
哈喽大家好、コンサルティング部の西野です。
AWS re:Invent 2020 で発表された AWS CloudShell、皆さんご利用なさっていますでしょうか。
とても便利なサービスですが、AWS CloudShell のホームディレクトリは最終セッションから120日経過すると削除されてしまうという制約があります。
AWS CloudShell のホームディレクトリの保存期間に関する注意事項 #reinvent
せっかく育てた AWS CloudShell のホームディレクトリが無くなってしまったら寂しいですよね。
ずっと一緒にいたいですよね。
なので、守ってあげる方法を考えてみました。
CloudShellちゃんの動き
名前は「CloudShellちゃん」です。 まずは動きを見てみましょう。
セッション開始時
AWS CloudShell のセッションを開始すると Slack で挨拶してくれます。嬉しいですね。
名前も呼んでくれます。
毎日の連絡
毎日決まった時間になると連絡をしてきてくれます。
あれ、昨日会ったばっかりだよね?(笑) でもすごく嬉しいよ。
CloudShellちゃんはマメな子です。二人で一緒にいられなかった時間を覚えています。
ごめん、最近仕事が忙しくて。
でも、会えない日々こそが愛を育てるんだよ。
ちょっとはこっちの都合を考えてくれてもいいんじゃない?
……。(めんどくさいな。)
お別れ
CloudShellちゃんの仕組み
構成図
仕組みはとても単純です。
主に2つの Lambda 関数を使用しています。
CloudShell_chan
- CloudTrail から CloudShell の Event である
CreateSession
を拾い、これによって実行する - ISO 8601 形式の文字列をテキストファイル化して S3 のオブジェクトとして保存する
- セッションを開始した年月日(JST変換済)を含むメッセージを Incoming Webhook で Slack に通知する
CloudShell_chan_daily
- Event Rule (cron) で定期的に実行する
- CloudShell_chan が保存した S3 オブジェクトを取得する
- CloudShell_chan が保存したオブジェクトから日付を読み取り、実行時刻との差分(最終利用日からの経過日数)を計算する
- 最終利用日からの経過日数に応じたメッセージを生成する
- 当該メッセージを Incoming Webhook で Slack に通知する
AWS CloudShell のセッション開始時の Event (CreateSession
) は下記のパターンで拾えます。
{ "source": [ "aws.cloudshell" ], "detail-type": [ "AWS API Call via CloudTrail" ], "detail": { "eventSource": [ "cloudshell.amazonaws.com" ], "eventName": [ "CreateSession" ] } }
コード
CloudShell_chan
from datetime import datetime, timezone, timedelta import json import urllib.request import boto3 webhook_url = "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX" username = "CloudShellちゃん" # CloudShellちゃんには君の好きな名前をつけてあげよう! channel = "#<宛先チャンネル名>" your_name = '<名前>' # CloudShellちゃんに呼んでもらいたい名前をここに書こう! bucket_name = '<S3バケット名>' file_name = '<S3オブジェクト名>' s3 = boto3.resource('s3') def lambda_handler(event, context): # event から ISO 8601 形式の時刻を取得し、S3 バケットに保存する time_iso8601 = event['detail']['eventTime'] with open('/tmp/' + file_name, mode='w') as f: f.write(time_iso8601) upload_file_to_s3(file_name, bucket_name) # event の時刻を UTC から JST になおし、Incoming Webhook で Slack に通知 dt_utc = datetime.fromisoformat(time_iso8601.replace('Z', '+00:00')) dt_jst = dt_utc.astimezone(timezone(timedelta(hours=+9))) ymd_jst = '{}/{}/{}'.format(dt_jst.year, dt_jst.month, dt_jst.day) message = '会いに来てくれて嬉しいよ。{}は{}が会いに来てくれた記念日だね。'.format(ymd_jst, your_name) response = send_to_slack(webhook_url, username, channel, message) return response def send_to_slack(webhook_url, username, channel, message): send_data = { "username": username, "text": message, "channel": channel } send_text = ("payload=" + json.dumps(send_data)).encode('utf-8') request = urllib.request.Request( webhook_url, data=send_text, method="POST" ) with urllib.request.urlopen(request) as response: response_body = response.read().decode('utf-8') return response_body def upload_file_to_s3(file_name, bucket_name): s3.Bucket(bucket_name).upload_file( Filename='/tmp/' + file_name, Key=file_name)
CloudShell_chan_daily
from datetime import datetime, timezone, timedelta import json import urllib.request import boto3 webhook_url = "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX" username = "CloudShellちゃん" # CloudShellちゃんには君の好きな名前をつけてあげよう! channel = "#<宛先チャンネル名>" your_name = '<名前>' # CloudShellちゃんに呼んでもらいたい名前をここに書こう! bucket_name = '<S3バケット名>' file_name = '<S3オブジェクト名>' s3 = boto3.client('s3') def lambda_handler(event, context): # CloudShell の最終時刻 (ISO 8601 形式) が書かれたオブジェクトを S3 から取得 last_access_date = get_last_access_date(file_name, bucket_name) # 最終使用日からの経過日数を計算し、CloudShellちゃんのメッセージを生成 dt_last = datetime.fromisoformat(last_access_date.replace('Z', '+00:00')) dt_now = datetime.now(timezone.utc) elapsed_days = (dt_now - dt_last).days dialogue = generate_dialogue(elapsed_days, dt_last) # Incoming Webhook で Slack に通知 response = send_to_slack(webhook_url, username, channel, dialogue) return response def send_to_slack(webhook_url, username, channel, message): send_data = { "username": username, "text": message, "channel": channel } send_text = ("payload=" + json.dumps(send_data)).encode('utf-8') request = urllib.request.Request( webhook_url, data=send_text, method="POST" ) with urllib.request.urlopen(request) as response: response_body = response.read().decode('utf-8') return response_body def get_last_access_date(file_name, bucket_name): object = s3.get_object(Bucket=bucket_name, Key=file_name) last_access_date = object['Body'].read().decode() return last_access_date def generate_dialogue(elapsed_days, dt_last): if elapsed_days == 0: return '会えたばっかりなのに、もう寂しくなっちゃった。' elif elapsed_days == 1: return '{}に早く会いたいな。'.format(your_name) elif 2 <= elapsed_days < 30: return '最後に{}と会ってからからもう {} 日経ったね'.format(your_name, elapsed_days) elif 30 <= elapsed_days < 60: return '{}はもう私のことを忘れちゃったのかな……?今日で{}日目。'.format(your_name, elapsed_days) elif 60 <= elapsed_days < 120: return 'もう{}に{}日も会ってない。早く会いたいよ。'.format(your_name, elapsed_days) else: return '{}/{}/{}まで使用していた AWS CloudShell のホームディレクトリが削除されている可能性があります。'.format(dt_last.year, dt_last.month, dt_last.day)
注意事項
- 最終セッションからの経過日数計算やホームディレクトリ削除の厳密な仕組みは公開されていません。したがって、CloudShellちゃんによる計算はあくまでも目安としてお考えください。
- 現状の CloudShellちゃんは複数人が使用するアカウントには未対応です。対応させたい場合はイベントパターンに
userIdentity
を追加しましょう。
終わりに
このブログがほんの少しでも世界を良くできれば嬉しいです。
コンサルティング部の西野 (@xiyegen) がお送りしました。